/*                          UX-Framework                               
 *                                                                 
 *  iZE Co.,Ltd. Copyright 2009 All rights reserved.          
 *  This file is distributed under the terms of the GNU Lesser 
 *  General Public License, described in file LICENSE.                                     
 */
package jp.ize.ux.acts;

// {{{ imports
import jp.ize.ux.UXObject;
import jp.ize.ux.components.UXCell;
import jp.ize.ux.acts.Act;
import jp.ize.ux.geom.UXPoint;
import jp.ize.ux.geom.UXSize;
import jp.ize.ux.utils.Util;
import jp.ize.ux.tween.UXTween;
// }}}
// {{{ typedef TRange
typedef TRange =
{
	var min:TPos;
	var max:TPos;
}
// }}}
// {{{ enum TableMoveDirection {
enum TableMoveDirection {
	/** row方向に移動させます */
	ROW;
	/** col方向に移動させます */
	COL;
}
// }}}
// {{{ typedef TableLayoutMovable
/**
 * TableLayoutActが必要な情報です。
 */
typedef TableLayoutMovable = {
	> ListStateType,
	/** */
	var rows:Int;
	/** */
	var cols:Int;
	/** */
	var headers:Array<UXCell>;
	/** 子要素 */
	var items(default, default) : Array<Array<UXCell>>;
	/** 矩形領域 */
	var size(default, default) : UXSize;
}
// }}}
// {{{ typedef TableMoveParameter
typedef TableMoveParameter = {
	> ActParameter,
	var dir:TableMoveDirection;
	var delta:Int;
}
// }}}
// {{{ typedef TableWipeParameter
typedef TableWipeParameter = {
	> ActParameter,
	var center:TPos;
}
// }}}
// {{{ typedef TableZoomParameter
typedef TableZoomParameter = {
	> ActParameter,
	var pos:TPos;
	var scale:Float;
}
// }}}
// {{{ class TableLayoutAct extends Act
/**
 * TableLayoutActクラス<br>
 * 直線上の動きを表現するクラスです。<br>
 *
 * @author		iZE Co., Ltd.<br>
 * @version		$Id$<br>
 */
class TableLayoutAct extends Act
{
	// {{{ public function new()
	/**
	 * コンストラクタ。
	 */
	public function new()
	{
		super();
	}
	// }}}
	// {{{ public function layout( ?ap:ActParameter, ?interp:TableLayoutMovable->ActParameter ) : TableLayoutMovable -> Void
	/**
	 * 直線上に子コンポーネントを並べるアクションです。
	 */
	public function layout( ?ap:ActParameter, ?interp:TableLayoutMovable->ActParameter ) : TableLayoutMovable -> Void
	{
		var me = this;
		return function( state : TableLayoutMovable ) : Void {
			var param = ap;
			if ( interp!=null ) param = interp(state);
			me.doLayout(state,param);
			me.retouchZ(state,{row:0,col:0});
		};
	}
	// }}}
	// {{{ public function move( ?mp:TableMoveParameter, ?interp:TableLayoutMovable->TableMoveParameter ) : TableLayoutMovable -> Void
	/**
	 * 子コンポーネントを移動量分だけ移動させるアクションです。
	 * stateのcyclicがtrueの場合でないと動作しません。
	 */
	public function move( ?mp:TableMoveParameter, ?interp:TableLayoutMovable->TableMoveParameter ) : TableLayoutMovable -> Void
	{
		var me = this;
		return function( state : TableLayoutMovable ) : Void {
			if ( state.cyclic ) {
				var param = mp;
				if ( interp!=null ) param = interp(state);

				var max:Int = 0;
				switch( param.dir ) {
					case ROW:
						max = state.rows;
					case COL:
						max = state.cols;
				}
				var indexes:Array<Int> = new Array();
				var ary:Array<Array<UXCell>> = new Array();
				for ( i in 0...max ) {
					ary.push( null );
				}
				for ( i in 0...max ) {
					// {{{ judge and preparing move
					var dir:Int = 0;
					var idx:Int = 0;
					if ( i+param.delta<0 ) {
						idx = i+param.delta+max;
						dir = -1 * Std.int(Math.abs(param.delta));
					} else if ( i+param.delta>max-1 ) {
						idx = (i+param.delta)-max;
						dir = Std.int(Math.abs(param.delta));
					} else {
						idx = i+param.delta;
					}
					switch ( param.dir ) {
						case ROW:
							ary[idx] = state.items[i].copy();
						case COL:
							var tmp:Array<UXCell> = new Array();
							for ( j in 0...state.items.length ) {
								tmp.push(state.items[j][i]);
							}
							ary[idx] = tmp;
					}
					// }}}
					// {{{ around the table
					if ( dir>0 ) {
						if ( param.dir==ROW ) {
							for ( j in 0...state.cols ) {
								state.items[i][j].y = - 10;
							}
						} else {
							for ( j in 0...state.rows ) {
								state.items[j][i].x = - 10;
							}
						}
					} else if ( dir<0 ) {
						if ( param.dir==ROW ) {
							for ( j in 0...state.cols ) {
								state.items[i][j].y = state.size.height + 10;
							}
						} else {
							for ( j in 0...state.rows ) {
								state.items[j][i].x = state.size.width + 10;
							}
						}
					}
					// }}}
				}
				// {{{ swap
				switch ( param.dir ) {
					case ROW:
						for ( i in 0...state.items.length ) {
							state.items[i] = ary[i];
						}
					case COL:
						for ( j in 0...ary.length ) {
							var tmp2 = ary[j].copy();
							for ( i in 0...tmp2.length ) {
								state.items[i][j] = tmp2[i];
							}
						}
				}
				// }}}
				me.doLayout(state,param);
				me.retouchZ(state,{row:0,col:0});
			}
		};
	}
	// }}}
	// {{{ public function wipe( wp:TableWipeParameter, ?interp:TableLayoutMovable->TableWipeParameter ) : TableLayoutMovable -> Void
	/**
	 * 直線上を子コンポーネントが重なるように移動するアクションです。
	 */
	public function wipe( wp:TableWipeParameter, ?interp:TableLayoutMovable->TableWipeParameter ) : TableLayoutMovable -> Void
	{
		var me = this;
		return function( state : TableLayoutMovable ) : Void {
			me.initSync(state.items.length*state.items[0].length);
			me.setSyncDoneHandler( function(){
				if ( state.subject!=null ) {
					state.subject.notify(Act.STOP);
				}
			});
			var syncCount:Int = 0;
			var param = wp;
			if ( interp!=null ) param = interp(state);
			me.retouchZ(state,param.center);
			var cx:Float = state.items[param.center.row][param.center.col].x;
			var cy:Float = state.items[param.center.row][param.center.col].y;
			for ( row in 0...state.items.length) {
				for ( col in 0...state.items[row].length ) {
					if ( row==param.center.row && col==param.center.col ) continue;
					var c = state.items[row][col];
					var getter = function(obj:UXObject): {x:Float,y:Float} {
						return {x:obj.x,y:obj.y};
					};
					var setter = function(obj:UXObject, init:{x:Float,y:Float}, end:{x:Float,y:Float}, v:Float) {
						obj.x = init.x+(end.x-init.x)*v;
						obj.y = init.y+(end.y-init.y)*v;
					};
					var end = {x:cx,y:cy};
					var tw = new UXTween<UXObject,{x:Float,y:Float}>(c, end, getter, setter, param.time,
								param.transition);
					tw.setEndTweenHandler( function(e:Dynamic){
						me.doneSync(syncCount);
					});
					syncCount++;
					var id = "tablewipe" + Std.string(row) + ":" + Std.string(col);
					if ( !me.tm.objects.exists(id) ) {
						me.tm.registObject(id,state);
					}
					me.tm.addTweenChain(id,tw); 
					me.tm.start(id,false,state.target);
				}
			}
		};
	}
	// }}}
	// {{{ public function zoom( fp:TableZoomParameter, ?interp:TableLayoutMovable->TableZoomParameter ) : TableLayoutMovable -> Void
	/**
	 * マウスの動きに合わせて魚眼ズームするアクションです。周りの子コンポーネントはつられて押し出されます。
	 */
	public function zoom( tzp:TableZoomParameter, ?interp:TableLayoutMovable->TableZoomParameter ) : TableLayoutMovable -> Void
	{
		var me = this;
		return function( state : TableLayoutMovable ) : Void {
			var param = tzp;
			if ( interp!=null ) param = interp(state);
			var c = state.items[param.pos.row][param.pos.col];
			c.scaleX = param.scale;
			c.scaleY = param.scale;
			me.retouchZ(state,param.pos);
			if ( state.subject!=null ) {
				state.subject.notify(Act.STOP);
			}
		};
	}
	// }}}
	// {{{ public function retouchZ( state:TableLayoutMovable, center:TPos ) : Void
	/**
	 * 子要素の前後関係を調整します
	 */
	public function retouchZ( state:TableLayoutMovable, center:TPos ) : Void
	{
		var ary:Array<TPos> = getFootPrint(state,center);
		/*
		*  9: -->                       d1:|
		*  8:(-1,-1) 1:( 0,-1) 2:( 1,-1)   |
		*  7:(-1, 0) 0:( 0, 0) 3:( 1, 0)   V
		*  6:(-1, 1) 5:( 0, 1) 4:( 1, 1)
		* d3: <--                   d2: <--
		*/
		ary.reverse();
		for ( i in 0...ary.length ) {
			var c = state.items[ary[i].row][ary[i].col];
			state.items[0][0].parent.setChildIndex( c, i );
		}
	}

	// }}}
	// {{{ public function doLayout( state:TableLayoutMovable, param:ActParameter ) : Void
	/**
	 * 実際のレイアウト・アニメーションを実装します
	 */
	public function doLayout( state:TableLayoutMovable, param:ActParameter ) : Void
	{
		var me = this;
		initSync(state.items.length*state.items[0].length);
		setSyncDoneHandler( function(){
			if ( state.subject!=null ) {
				state.subject.notify(Act.STOP);
			}
		});
		var dx:Float = state.size.width/state.cols;
		var dy:Float = state.size.height/state.rows;
		var syncCount:Int = 0;
		for (i in 0...state.items.length) {
			for ( j in 0...state.items[i].length ) {
				var c = state.items[i][j];
				var getter = function(obj:UXObject): TPoint {
					return {x:obj.x,y:obj.y};
				};
				var setter = function(obj:UXObject, init:TPoint, end:TPoint, v:Float) {
					obj.x = init.x+(end.x-init.x)*v;
					obj.y = init.y+(end.y-init.y)*v;
				};
				var end = {x: dx*j, y:dy*i };
				var tw = new UXTween<UXObject,TPoint>(c, end, getter, setter, param.time,
							param.transition);
				tw.setEndTweenHandler( function(e:Dynamic){
					me.doneSync(syncCount);
				});
				syncCount++;
				var id = "tablelayout" + Std.string(i) + ":" + Std.string(j);
				if ( !me.tm.objects.exists(id) ) {
					me.tm.registObject(id,state);
				}
				me.tm.addTweenChain(id,tw); 
				me.tm.start(id,false,state.target);
			}
		}
	}

	// }}}
	// {{{ public function getFootPrint( state:TableLayoutMovable, base:TPos ) : Array<TPos>
	/**
	* 要素を右回りに中心から外部に向かって順番に並べたものを取得します。
	*/
	public function getFootPrint( state:TableLayoutMovable, base:TPos ) : Array<TPos>
	{
		var dirs:Array<TPos> = [
			{col: 0, row:-1},
			{col: 1, row: 0},
			{col: 0, row: 1},
			{col:-1, row: 0}
		];
		var s:TPos = {col:0, row:0};
		var cur:TPos = s;
		var cnt:Int = 0;
		var dir:Int = 0;
		var range:TRange = {
			min:{col:-1,row:-1},
			max:{col: 1,row: 1}
		}
		var rrange:TRange = {
			min:{col: 0,row: 0},
			max:{col:state.cols-1,row:state.rows-1}
		}
		var log:Array<TPos> = new Array();
		while ( cnt<(state.rows*state.cols)-1 ) {
			var newpos:TPos = null;
			var delta:TPos = dirs[dir%4];
			newpos = add(cur,delta);
			if ( !check(range,newpos) ) {
				dir++;
				delta = dirs[dir%4];
				newpos = add(cur,delta);
				if ( dir%4==0 ) {
					range.min = add(range.min,{col:-1,row:-1});
					range.max = add(range.max,{col: 1,row: 1});
				}
			}
			cur = newpos;
			var real:TPos = add(cur,base);
			if ( check( rrange, real ) ) {
				log.push( real );
				cnt++;
			}
		}
		/*
		var z:Array<Array<Int>> = new Array();
		for ( i in 0...rows ) {
			var ary:Array<Int> = new Array();
			for ( j in 0...cols ) {
				ary.push(0);
			}
			z.push(ary);
		}
		for ( i in 0...log.length ) {
			var p = log[i];
			z[p.y][p.x] = i+1;
		}
		for ( i in 0...rows ) {
			for ( j in 0...cols ) {
				neko.Lib.print(StringTools.lpad(Std.string(z[i][j])," ",3));
			}
			neko.Lib.println("");
		}
		*/
		return log;
	}

	// }}}
	// {{{ public function add( p1:TPos, p2:TPos ) : TPos
	/**
	* 座標の足し算です。
	*/
	public function add( p1:TPos, p2:TPos ) : TPos
	{
		return {col:p1.col+p2.col, row:p1.row+p2.row};
	}

	// }}}
	// {{{ public function check( r:TRange, p:TPos ) : Bool
	/**
	* 座標が範囲内にあるかどうかのチェック関数です。
	*/
	public function check( r:TRange, p:TPos ) : Bool
	{
		if ( p.col>=r.min.col && p.row>=r.min.row && p.col<=r.max.col && p.row<=r.max.row ) {
			return true;
		} else {
			return false;
		}
	}

	// }}}
}
// }}}
